from pandac.PandaModules import *
from direct.interval.MetaInterval import *

from direct.task import Task
from direct.actor import Actor

import random, math
import xml.sax

verson = '0.0.1'

# local imports
import tables
#import colors
#from gfx import *

gamestring = "Maultasche ", version

respair = (1024, 768)

class Point(Point2):
    def __init__(self, x=0.0, y=0.0):
        Point2.__init__(self, x, y)
    
    def distance(self, pt2):
        '''Returns distance from this point to pt2'''
        my_x = (self.x - pt2.x)
        my_y = (self.y - pt2.y)

        #return sqrt( (my_x * my_x) + (my_y * my_y) )
        return math.hypot(my_x,my_y)

    def distance2(self, pt2):
        '''Returns squared distance from this point to pt2'''
        my_x = (self.x - pt2.x)
        my_y = (self.y - pt2.y)

        return (my_x*my_x) + (my_y*my_y)

    def angleto(self, pt2):
        '''Returns the angle from this point to pt2'''
        my_x = self.x - pt2.x
        my_y = self.y - pt2.y

        # dir = -(180.0/3.14159) * atan2(ship.x-x,ship.y-y)

        return math.atan2(my_x,my_y)


def angleto(pt1, pt2):
        '''Returns the angle from this point to pt2'''
        my_x = pt1.x - pt2.x
        my_y = pt1.y - pt2.y

        # dir = -(180.0/3.14159) * atan2(ship.x-x,ship.y-y)

        return math.atan2(my_x,my_y)

def vectorTo(pt1, pt2):
    my_x = pt1.x - pt2.x
    my_y = pt1.y - pt2.y

    return ogre.Vector3(my_x, my_y, 0)

def hit(unit1, unit2):
    # NOTE: damage calculation formula
    damage = int(unit1.av - unit2.dv)
    unit2.hp -= damage

class City:
    def __init__(_):
        _.basemesh = "platform.egg"
        _.keepmesh = "keep.egg"
        
        _.rank = 1
        
        # start from upper-left, going clockwise
        _.buildings = []

    def build(_, newbuilding):
        # see if we have the money for the building
        # assign it a spot
        _.buildings.append(newbuilding)
        # start building
    
    def update(_, timestep):
        #if a building is under construction, cycle its build timer
        pass

class Building:
    def __init__(_):
        pass
        
class Unit(Actor.Actor):
    uid = -1
    
    def __init__(_, Scene, uid, unitname, teamname = "Independents"):
        _.intPos = [0,0]
        _.pos = Point()

        _.heading = 0.0

        _.fcolor = (0.6, 0.6, 0.6)
        _.bcolor = (1.0, 1.0, 1.0)
        
        # <FIXME> : currently using python modules for designing units.
        # perhaps we should move to XML or something in the future
        template = tables.units[unitname]
        # </FIXME>
        
        _.name = template["name"]
        _.mesh = template["mesh"]
        _.hp = template["hp"]
        _.ranged_av = template["ranged_av"]
        _.av = template["melee_av"]
        _.dv = template["dv"]

        # default speed is one square a second... I guess.
        _.basespeed = template["speed"]
        _.attack_delay = template["rof"]
                
        _.attack_recharge = 0
        _.xp = 0

        # command-related stuff
        _.destination = None
        _.target = None
        _.action = None
            
        #Scene = newScene;//Singleton<SceneManager>::getSingletonPtr();
        _.uid = uid
        #_.uid = uid_counter
        #uid_counter += 1

        # entity generation (graphics)
        _.ent = Scene.createEntity(str(_.uid), _.mesh)
        _.ent.setMaterialName("Maultasche/PseudoFlat")

    ##    clone the material now
    ##    FIXME FUTURE : Materials will be handled much more efficiently...
    ##    they will only get cloned on a per-unique-mesh and/or a per-team basis
    ##    therefore, this block of code should be in another class and instead
    ##    replaced with a 'setmaterialname' referring to these other materials
        # use my team's material (color scheme)
        _.ent.setMaterialName("Teams/" + teamname)

        _.node = Scene.getRootSceneNode().createChildSceneNode()
        _.node.attachObject(_.ent)

        # body generation (physics)


        # set up objects at 0,0,0 by default
        _.node.setPosition(0,0,0)
        _.pos = _.node.getPosition()
        # we have to do some rotations so the unit will show up right
        _.node.roll(ogre.Degree(180))
        _.node.pitch(ogre.Degree(90))

        mMaxSpeed = 9999.0

    def copy(_):
        return Unit.__init__(_)

    def loadFromXML(_, xmlfile, unitname):
        parser = xml.sax.make_parser()

        # not interested in XML namespaces
        parser.setFeature(xml.sax.handler.feature_namespaces, 0)

        myhandler = XMLUnitHandler(unitname)

        parser.setContentHandler(myhandler)

        parser.parse(xmlfile)

    def getPos(_):
        return _.pos
    
    def getIntPos(_):
        return _.intPos

    def setPos(_, pos):
        _.intPos = [ int(pos.x), int(pos.y) ]
        try:
            _.pos = pos
            _.node.setPosition(_.pos.x, _.pos.y, 0)
        except:
            pass
    
    def setPos(_, x, y):
        _.intPos = [ int(x), int(y) ]
        try:
            _.pos = Point(x, y) 
            _.node.setPosition(_.pos.x, _.pos.y, 0)
        except:
            pass        
        

    def update(_, frametime):
        ''' functions to be run every cycle '''
        # Hmm... let's see if I'm dead or not
        if _.hp <= 0:
            _.die() # yup! ARGH!
            return
        
        # count down the delay between attacks
        if _.attack_recharge > 0:
            _.attack_recharge -= _.attack_delay * frametime
            
            # if we just subtracted it into negatives, fix it
            if _.attack_recharge < 0:
                _.attack_recharge = 0

##        # figure out where I'm going
##        if _.destination is not None:
##            _.goTo(frametime, _.destination)
##
##            # nullify the destination when we get there
##            if _.destination == _.getPos():
##                _.destination = None


        # figure out what I'm doing
        if _.target is not None:
            if _.action is "go":
                _.goTo(frametime, _.target)

                # nullify the destination when we get there
                if _.target == _.getPos():
                    _.target = None
            
            elif _.action is "attack":
                if distance(_.getPos(), _.target.getPos()) <= 1:
                    _.attack(_.target)
                else:
                    _.goTo(frametime, _.target.getPos())


    def die(_):
        # first of all, make sure HP is zero
        _.hp = 0
        del _

    def attack(_, enemyunit):
        if _.attack_recharge <= 0:
            # FIXME : replace this with real range
            if distance(_.getPos(), enemyunit.getPos()) <= 1:
                _.attack_recharge = _.attack_delay
                # FIXME: replace this later with something OO 
                hit(_, enemyunit)

    def calcRealSpeed(_):
        # take the base speed and apply modifiers...
        realspeed = _.basespeed

        # terrain
        # formation
        # special effects (charging, retreating, etc)
        
        return realspeed
        
    def move(_, time):
        if _.destination is None:
            return
        
        realspeed = _.calcRealSpeed()

        #_.node.translate(xvel, yvel, 0)
        _.node.translate(0, 0, -realspeed)
        newpos = _.node.getPosition()
        _.pos.x = newpos.x
        _.pos.y = newpos.y

    def goTo(_, destination):
        _.destination = destination
        _.heading = angleto(_.pos, _.destination)
        # set the new heading in world space
        _.node.setDirection(vectorTo(_.pos, _.destination),
                            ogre.SceneNode.TS_WORLD)

class Officer(Unit):
    pass

class Formation:
    def __init__(_):
        _.front = []
        _.flank = []

combatFormation = Formation()
combatFormation.front = (
    (-2, 1),
    (-1, 1),
    ( 1, 1),
    ( 2, 1)
)
combatFormation.flank = (
    (-2, 0),
    ( 2, 0)
)


class Company:
    def __init__(_, front, flank, officer):
        # make sure all of these guys are units
        if not isinstance(front, Unit):
            return
        
        if not isinstance(flank, Unit):
            return
        
        if not isinstance(officer, Unit):
            return
        
        _.front = [front, front, front, front]
        _.flank = [flank, flank]
        _.officer = officer

        # make it so the entire company travels only as fast as the
        # slowest unit
        #_.basespeed =
        
        _.formation = combatFormation

    def goTo(_, destination, heading=None):
        # if there was no direction given for the formation to face,
        # make one up
        if heading is None:
            heading = angleto(_.getPos(), destination)

        # figure out where everyone should be standing at the destination
        
        # issue a series of goTo commands to the constituent units


class Player:
    def __init__(_, pcolor, scolor):
        _.scolor = scolor
        _.pcolor = pcolor

        _.faction = "Wei"
 

class UnitManager:
    def __init__(_, sceneManager):
        _.entities = []
        _.sceneMgr = sceneManager
        _.uid_counter = 0

    def addUnit(_, newunit):
        _.entities.append(newunit)

    def createUnit(_, unitname, team="Independents"):
        newguy = Unit(_.sceneMgr, _.uid_counter, unitname, team)
        _.entities.append(newguy)
        
        _.uid_counter += 1
        return newguy

    def selectUnitAt(_, pos):
        """ Returns None if no unit exists there"""
        for unit in _.entities:
            if unit.getPos() == pos:
                return unit

        return None

    def update(_,timestep):
        # do per-unit updates
        for unit in _.entities:
            # move units
            unit.update(timestep)
            unit.move(timestep)

fullscreen = False
desired_fps = 60.0

# all in radians, by the way, to avoid having to convert
dirNE = 5.497787143782138 # 315
dirN = 4.7123889803846897 # 270
dirNW = 3.9269908169872414 # 225
dirW = 3.1415926535897931 # 180
dirSW = 2.3561944901923448 # 135
dirS = 1.5707963267948966 # 90
dirSE = 0.78539816339744828 # 45
dirE = 0.0